#include "cybertron/core/UnlimitedInteger.hpp"

CYBERTRON_BEGIN

UnlimitedInteger::UnlimitedInteger() : _numberString(std::string("0"))
{
}

UnlimitedInteger::UnlimitedInteger(std::string number)
{
	for (int i = 0; i < number.size(); i++)
	{
		if (i == 0 && number[i] == '-')
			continue;
		if (number[i] < '0' || number[i] > '9')
		{
			logError("UnlimitedInteger can't handle non-number char");
			_numberString = std::string("0");
			return;
		}
	}
	_numberString = number;
	trimLeadingZeros();
}

UnlimitedInteger::UnlimitedInteger(long long number) : _numberString(std::to_string(number))
{
}

UnlimitedInteger::UnlimitedInteger(const UnlimitedInteger & other) : _numberString(other._numberString)
{
}

std::string UnlimitedInteger::getString() const
{
	return _numberString;
}

size_t UnlimitedInteger::digits() const
{
	return _numberString.length() - (isNegative() ? 1 : 0);
}

bool UnlimitedInteger::isNegative() const
{
	return _numberString[0] == '-';
}

UnlimitedInteger UnlimitedInteger::negate()
{
	if (isNegative())
		_numberString.erase(0, 1);
	else if (_numberString != std::string("0"))
		_numberString.insert(_numberString.begin(), '-');
	return *this;
}

bool UnlimitedInteger::equals(const UnlimitedInteger & other)
{
	return _numberString == other._numberString;
}

UnlimitedInteger UnlimitedInteger::trimLeadingZeros()
{
	size_t i = 0;
	if (isNegative())
		i = 1;
	while (i < _numberString.size())
	{
		if (_numberString[i] == '0')
			_numberString.erase(i, 1);
		else
			break;
	}
	if (_numberString == std::string("") ||
		_numberString == std::string("-0") ||
		_numberString == std::string("-"))
		_numberString = std::string("0");
	return *this;
}

bool UnlimitedInteger::greater(const UnlimitedInteger & u2)
{
	UnlimitedInteger u1 = *this;
	if (u1.isNegative() || u2.isNegative()) {
		if (u1.isNegative() && u2.isNegative()) {
			UnlimitedInteger tmp = u2;
			u1.negate();
			tmp.negate();
			return u1 < tmp;
		}
		else
			return !(u1.isNegative() && !u2.isNegative());
	}
	else if (u1 == u2)
		return false;
	else if (u1.digits() > u2.digits())
		return true;
	else if (u2.digits() > u1.digits())
		return false;
	else {
		for (unsigned int i = 0; i < u1._numberString.size(); ++i) {
			if (u1._numberString[i] == u2._numberString[i])
				continue;
			return u1._numberString[i] > u2._numberString[i];
		}
	}
	return false;
}

UnlimitedInteger UnlimitedInteger::add(UnlimitedInteger other)
{
	UnlimitedInteger u1 = other > *this ? other : *this;
	UnlimitedInteger u2 = other > *this ? *this : other;
	if (u1.isNegative() || u2.isNegative()) {
		if (u1.isNegative() && u2.isNegative())
			return u1.negate().add(u2.negate()).negate();
		else if (u1.isNegative() && !u2.isNegative())
			return u1.negate().subtract(u2).negate();
		else
			return u2.negate().subtract(u1).negate();
	}
	std::string results;
	int carry = 0;
	int diff = int(u1._numberString.size() - u2._numberString.size());
	for (int i = 0; i < diff; ++i)
		u2._numberString.insert(u2._numberString.begin(), '0');
	for (int i = int(u1._numberString.size() - 1); i >= 0; --i) {
		int sum = (u1._numberString[i] - '0') + (u2._numberString[i] - '0') + carry;
		carry = 0;
		if (sum <= 9 || i == 0)
			results.insert(0, std::to_string(sum));
		else {
			results.insert(0, std::to_string(sum % 10));
			carry = 1;
		}
	}
	return UnlimitedInteger(results);
}

UnlimitedInteger UnlimitedInteger::subtract(UnlimitedInteger other)
{
	if (equals(other))
	{
		_numberString = std::string("0");
		return *this;
	}
	UnlimitedInteger u1 = *this, u2 = other;
	if (u1.isNegative() || u2.isNegative()) {
		if (u1.isNegative() && u2.isNegative())
			return u1.negate().subtract(u2.negate()).negate();
		else if (u1.isNegative() && !u2.isNegative())
			return u1.negate().add(u2).negate();
		else
			return u2.negate().add(u1);
	}
	std::string results;
	int n = 0, p = 0;
	bool takeOffOne = false;
	bool shouldBeTen = false;
	if (u1 < u2) {
		std::string t = u2.subtract(*this).negate().getString();
		for (unsigned int i = 1; i < t.length(); ++i) {
			if (t[i] != '0') break;
			t.erase(1, 1);
		}
		return UnlimitedInteger(t);
	}

	if (u1._numberString.size() - u2.getString().size() > 1) {
		for (unsigned long i = 0; i < u1._numberString.size() - u2.getString().size() - 1; ++i) {
			u2._numberString.insert(u2._numberString.begin(), '0');
		}
	}
	int i = int(u1._numberString.size() - 1);
	for (int j = int(u2._numberString.size() - 1); j >= 0; --j) {
		if (((u1._numberString[i] - '0') < (u2._numberString[j] - '0')) && i > 0) {
			n = char((u1._numberString[i] - '0') + 10);
			takeOffOne = true;
			if (j > 0 || u1._numberString[i - 1] != '0') {
				p = char((u1._numberString[i - 1] - '0') - 1);
				if (p == -1) {
					p = 9;
					shouldBeTen = true;
				}
				takeOffOne = false;
			}
			if (shouldBeTen) {
				int index = i - 1;
				for (int a = i - 1; (u1._numberString[a] - '0') == 0; --a) {
					u1._numberString[a] = static_cast<char>(p + '0');
					--index;
				}
				int t = (u1._numberString[index] - '0') - 1;
				u1._numberString[index] = static_cast<char>(t + '0');
			}
			u1._numberString[i - 1] = static_cast<char>(p + '0');
			shouldBeTen = false;
		}
		std::stringstream ss;
		if (((u1._numberString[i] - '0') == (u2._numberString[j] - '0'))) {
			ss << "0";
		}
		else {
			if (n <= 0) {
				ss << ((u1._numberString[i] - '0') - (u2._numberString[j] - '0'));
			}
			else {
				ss << (n - (u2._numberString[j] - '0'));
			}
		}

		results.insert(0, ss.str());
		--i;
		n = 0;
	}
	if (takeOffOne) {
		std::string number = "";
		for (int j = (int)u1._numberString.length() - (int)u2._numberString.length() - 1; j >= 0; --j) {
			if (u1._numberString[j] == '0') {
				number += "0";
				continue;
			}
			else {
				number.insert(number.begin(), u1._numberString[j]);
				int t = atoi(number.c_str());
				--t;
				u1._numberString.replace(0, number.size(), std::to_string(t));
				break;
			}
		}
	}
	while (i >= 0) {
		std::stringstream ss;
		if (i == 0) {
			if (u1._numberString[i] - '0' != 0) {
				ss << (u1._numberString[i] - '0');
				results.insert(0, ss.str());
			}
		}
		else {
			ss << (u1._numberString[i] - '0');
			results.insert(0, ss.str());
		}

		--i;
	}

	if (results.find_first_not_of('0') == std::string::npos) {
		results = "0";
	}
	else if (results[0] == '0') {
		size_t index = results.find_first_not_of('0');
		results = results.substr(index, results.length() - 1);
	}
	return UnlimitedInteger(results);
}

CYBERTRON_END